(library (parallelism)
  (export run-in-parallel
          map-indexed
          parallel-map)
  (import
   (except (rnrs base) let-values map)
   (only (guile)
         lambda* λ)
   (ice-9 futures)
   (srfi srfi-1))

  (define map-indexed
    (λ (proc lst)
      "Map PROC to all elements of LST, but also give the index of
each element to the mapped PROC as an argument. PROC is
called (proc elem index)."
      (let iter ([ind 0] [lst° lst])
        (cond
         [(null? lst°) '()]
         [else
          (cons (proc (car lst°) ind)
                (iter (+ ind 1) (cdr lst°)))]))))

  (define run-in-parallel
    (λ (segments map-indexed-proc reduce-proc reduce-init)
      "Use futures to run a procedure in parallel, if
multiple cores are available. Take a list of SEGMENTS as
input, which are ranges of values to work on. MAP-PROC is
applied to the SEGMENTS using map. When the MAP-PROC calls
for all segments finished and returned values, the
REDUCE-PROC is applied to the map result using reduce and
the REDUCE-INIT argument."
      (let ([futures
             (map-indexed
              (λ (seg ind)
                (make-future
                 ;; Need to wrap in a thunk, to not
                 ;; immediately start evaluating.
                 (λ () (map-indexed-proc seg ind))))
              segments)])
        (let ([segment-results (map touch futures)])
          (reduce reduce-proc
                  reduce-init
                  segment-results)))))

  (define parallel-map
    (λ (map-indexed-proc lst)
      "Parallelized version of map, if multiple cores are
available."
      (let ([futures
             (map-indexed (λ (elem ind)
                            (make-future (λ () (map-indexed-proc elem ind))))
                          lst)])
        (let ([results (map touch futures)])
          results)))))
